﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace GBricks.Collections
{
  partial class Comparers
  {
    public static IEqualityComparer<IEnumerable<T>> UnorderedCollection<T>(IEqualityComparer<T> itemComparer = null) {
      Contract.Ensures(Contract.Result<IEqualityComparer<IEnumerable<T>>>() != null);
      return UnorderedCollection<IEnumerable<T>, T>(itemComparer);
    }

    public static IEqualityComparer<TCollection> UnorderedCollection<TCollection, T>(IEqualityComparer<T> itemComparer = null) where TCollection : IEnumerable<T> {
      Contract.Ensures(Contract.Result<IEqualityComparer<TCollection>>() != null);
      return new UnorderedCollectionEqualityComparer<TCollection, T>(itemComparer);
    }

    [Serializable]
    private sealed class UnorderedCollectionEqualityComparer<TCollection, T> : EqualityComparer<TCollection> where TCollection : IEnumerable<T>
    {
      private static readonly IEqualityComparer<HashSet<T>> SetComparer = HashSetDefaultComparer<T>();

      public UnorderedCollectionEqualityComparer(IEqualityComparer<T> comparer = null) {
        ItemComparer = comparer ?? EqualityComparer<T>.Default;
      }

      private IEqualityComparer<T> ItemComparer { get; set; }

      [ContractInvariantMethod]
      private void Invariant() {
        Contract.Invariant(ItemComparer != null);
      }

      private HashSet<T> GetSet(TCollection item) {
        Argument.NotNull(item, "item");

        var set = item as HashSet<T>;
        if(set != null && Equals(set.Comparer, ItemComparer)) {
          return set;
        }//if

        return new HashSet<T>(item, ItemComparer);
      }

      public override bool Equals(TCollection x, TCollection y) {
        if(x == null) {
          return y == null;
        } else if(y == null) {
          return false;
        }//if

        var xset = GetSet(x);
        var yset = GetSet(y);
        return SetComparer.Equals(xset, yset);
      }

      public override int GetHashCode(TCollection obj) {
        if(obj == null) {
          return 0;
        }//if

        var set = GetSet(obj);
        return SetComparer.GetHashCode(set);
      }

      public override bool Equals(object obj) {
        var other = obj as UnorderedCollectionEqualityComparer<TCollection, T>;
        return other != null && Equals(other.ItemComparer, ItemComparer);
      }

      public override int GetHashCode() {
        return ItemComparer.GetHashCode();
      }
    }
  }
}
